Title Banner

Previous Book Contents Book Index Next

Inside Macintosh: QuickDraw GX Graphics /
Chapter 5 - Bitmap Shapes / Using Bitmap Shapes


Creating and Drawing Bitmaps

QuickDraw GX provides a number of methods to create and draw bitmaps. For example, you can

The next section, "Creating Black-and-White Bitmaps," and "Creating Color Bitmaps," which begins on page 5-21, show you how to create bitmaps by specifying the bitmap geometry yourself.

The section "Converting Other Types of Shapes to Bitmaps," which begins on page 5-34, shows you how you can create a bitmap shape containing a bitmap representation of other types of QuickDraw GX shapes.

The section "Creating Bitmaps Offscreen," which begins on page 5-45, shows you how you can draw other shapes into the pixel image of a bitmap shape. You can use this method to create a bitmap representation of multiple QuickDraw GX shapes.

For information about flattening and unflattening bitmap shapes, see the chapter "Shape Objects" in Inside Macintosh: QuickDraw GX Objects.

Creating Black-and-White Bitmaps

You create a black-and-white bitmap by creating a bitmap shape with a pixel size of 1. To do this, you can define a pixel image, fill the fields of a bitmap geometry structure, and create a bitmap shape using the GXNewBitmap function.

Listing 5-1 shows a complete sample function that defines a black-and-white bitmap geometry, creates a bitmap shape, draws the shape, and disposes of it.

Listing 5-1 Creating a black-and-white bitmap

void CreateBlackAndWhiteBitmap(void)
{
   gxShape  aBitmapShape;

   gxBitmap aBitmapGeometry;
   gxPoint initialPosition = {ff(20), ff(40)};
   
   const char envelopeImage[] = {0x7F, 0xFF, 0xFF, 0xFE,
                                 0xC0, 0x00, 0x00, 0x03,
                                 0xB0, 0x00, 0x00, 0x0D,
                                 0x8C, 0x00, 0x00, 0x31,
                                 0x83, 0x00, 0x00, 0xC1,
                                 0x80, 0xC0, 0x03, 0x01,
                                 0x80, 0x30, 0x0C, 0x01,
                                 0x80, 0x0C, 0x30, 0x01,
                                 0x80, 0x33, 0xCC, 0x01,
                                 0x80, 0xC0, 0x03, 0x01,
                                 0x83, 0x00, 0x00, 0xC1,
                                 0x8C, 0x00, 0x00, 0x31,
                                 0xB0, 0x00, 0x00, 0x0D,
                                 0x7F, 0xFF, 0xFF, 0xFE};

   
   aBitmapGeometry.image = (char *) aSmallBitmapImage;
   
   aBitmapGeometry.width = 32;       /* width in pixels */ 
   aBitmapGeometry.height = 14;      /* height in pixels */
   aBitmapGeometry.rowBytes = 4;     /* bytes per row */
   aBitmapGeometry.pixelSize = 1;    /* bits per pixel */
   
   /* QuickDraw GX creates a black-and-white color set for you */
   aBitmapGeometry.space = gxNoSpace; 
   aBitmapGeometry.set = nil;         
   aBitmapGeometry.profile = nil;     

   aBitmapShape = GXNewBitmap(&aBitmapGeometry, &initialPosition);

   GXDrawShape(aBitmapShape);
   GXDisposeShape(aBitmapShape);
}
The result of this function is shown in Figure 5-8.

Figure 5-8 A black-and-white bitmap--32 bits wide

The sample function from Listing 5-1 first defines a variable to hold the reference to the bitmap shape:

gxShape  aBitmapShape;
Then the sample function defines two local variables to specify the bitmap geometry:

gxBitmap aBitmapGeometry;
gxPoint initialPosition = {ff(20), ff(40)};
The initialPosition variable, which is type gxPoint, contains the initial bitmap position, and the aBitmapGeometry variable, which is type gxBitmap, contains the rest of the information about the bitmap.

The sample function then defines the bitmap's pixel image:

const char envelopeImage[] = {0x7F, 0xFF, 0xFF, 0xFE,
                              0xC0, 0x00, 0x00, 0x03,
                              0xB0, 0x00, 0x00, 0x0D,
                              0x8C, 0x00, 0x00, 0x31,
                              0x83, 0x00, 0x00, 0xC1,
                              0x80, 0xC0, 0x03, 0x01,
                              0x80, 0x30, 0x0C, 0x01,
                              0x80, 0x0C, 0x30, 0x01,
                              0x80, 0x33, 0xCC, 0x01,
                              0x80, 0xC0, 0x03, 0x01,
                              0x83, 0x00, 0x00, 0xC1,
                              0x8C, 0x00, 0x00, 0x31,
                              0xB0, 0x00, 0x00, 0x0D,
                              0x7F, 0xFF, 0xFF, 0xFE};
The envelopeImage variable, which is defined as an array of bytes, contains a pixel image depicting a small envelope, as shown in Figure 5-8.

To create a bitmap shape encapsulating this envelope image, the sample function fills in the eight fields of the aBitmapGeometry variable. First, it sets the image field by casting the envelopeImage variable to the correct type:

aBitmapGeometry.image = (char *) envelopeImage;
Then the sample function fills in the bitmap dimensions. The bitmap is 32 pixels wide by 14 pixels high, and there are 4 bytes of information in each row of the pixel image:

aBitmapGeometry.width = 32;       /* width in pixels */ 
aBitmapGeometry.height = 14;      /* height in pixels */
aBitmapGeometry.rowBytes = 4;     /* bytes per row */
The sample function specifies the pixel size next. Since this bitmap is black-and-white, only one bit is needed to represent each pixel of the bitmap:

aBitmapGeometry.pixelSize = 1;    /* bits per pixel */
Finally, the sample function specifies color information. Since QuickDraw GX does not provide a black-and-white color space, this bitmap needs a black-and-white color set in which pixel values of 0 represent white pixels and pixel values of 1 represent black pixels. Setting the pixelSize field to 1 and the space field to gxNoSpace indicates that QuickDraw GX should create this black-and-white color set for you.

aBitmapGeometry.space = gxNoSpace;  
aBitmapGeometry.set = nil;          
aBitmapGeometry.profile = nil;      
Setting the space field to the value gxNoSpace always indicates that QuickDraw GX should choose a color space for you. If the pixel size were large--for example, 16 or 32--QuickDraw GX would choose an RGB color space. However, since the pixel size is 1, no appropriate color space exists, so QuickDraw GX creates a grayscale color set. The pixel size determines the size of the color set created. In this case, a pixel size of 1 dictates that the color set have two entries--an white entry for a pixel value of 0 and a black entry for a pixel value of 1.

After you define a bitmap geometry, you could use the GXDrawBitmap function to cause QuickDraw GX to

with this line of code:

GXDrawBitmap(&aBitmapGeometry, &initialPosition);
You should use the GXDrawBitmap function, however, only when you know in advance that you want to draw a bitmap only one time.

If you want to draw a bitmap more than once, you should encapsulate the bitmap geometry in a bitmap shape and then draw the bitmap shape. The sample function in Listing 5-1 uses this method:

aBitmapShape = GXNewBitmap(&aBitmapGeometry, &initialPosition);
GXDrawShape(aBitmapShape);
As with any type of QuickDraw GX shape, if you create a bitmap shape, you are responsible for disposing of it when you no longer need it. Listing 5-1 does this by calling

GXDisposeShape(aBitmapShape); 
Notice that the envelope bitmap requires 4 bytes--an even number--to represent each row of the pixel image. However, to draw a similar envelope bitmap that includes two more rows of bits, as shown in Figure 5-10, the required number of bytes might seem to be 5 since 5 bytes contain 40 bits, more than enough needed to store the 34 bits per row in this image.

However, if you set the rowBytes field to 5:

aBitmapGeometry.rowBytes = 5;
both the GXDrawBitmap function and the GXNewBitmap function post the error bitmap_rowBytes_not_aligned, because the value of the rowBytes field must be an even number.

Therefore, the value of the rowBytes field must be at least 6 for the bitmap of the envelope with a shadow. However, simply setting the rowBytes field to the value 6 with the assignment

aBitmapGeometry.rowBytes = 6;
results in the bitmap shown in Figure 5-9.

Figure 5-9 An example of unaligned bytes per row

Clearly, the value of the bitmap's rowBytes field is not aligned with the data in the bitmap's pixel image. If you set the value of the rowBytes field to 6, you must be sure to pad the pixel image so that each row actually contains 6 bytes of information. Listing 5-2 shows a new definition of the pixel image. In this definition, each row contains one extra byte so that the total number of bytes per row is even.

In this example, the extra bytes are initialized to the value 0x00. However, since these bytes are just padding, you can specify any values for them. As indicated by the bitmap width, QuickDraw GX ignores these extra bytes when drawing, hit-testing, or otherwise manipulating the bitmap.

Listing 5-2 A bit image with an even number of bytes per row

static char envelopeImage[]= {0x7F, 0xFF, 0xFF, 0xFE, 0x00, 0x00,
                              0xC0, 0x00, 0x00, 0x03, 0x50, 0x00,
                              0xB0, 0x00, 0x00, 0x0D, 0xA0, 0x00,
                              0x8C, 0x00, 0x00, 0x31, 0x50, 0x00,
                              0x83, 0x00, 0x00, 0xC1, 0xA0, 0x00,
                              0x80, 0xC0, 0x03, 0x01, 0x50, 0x00,
                              0x80, 0x30, 0x0C, 0x01, 0xA0, 0x00,
                              0x80, 0x0C, 0x30, 0x01, 0x50, 0x00,
                              0x80, 0x33, 0xCC, 0x01, 0xA0, 0x00,
                              0x80, 0xC0, 0x03, 0x01, 0x50, 0x00,
                              0x83, 0x00, 0x00, 0xC1, 0xA0, 0x00,
                              0x8C, 0x00, 0x00, 0x31, 0x50, 0x00,
                              0xB0, 0x00, 0x00, 0x0D, 0xA0, 0x00,
                              0x7F, 0xFF, 0xFF, 0xFE, 0x50, 0x00,
                              0x15, 0x55, 0x55, 0x55, 0xA0, 0x00,
                              0x0A, 0xAA, 0xAA, 0xAA, 0x80, 0x00};
With this new, padded definition of the pixel image, you can set rowBytes field to 6 so that the resulting bitmap appears as shown in Figure 5-10.

Figure 5-10 An envelope with a shadow

For a discussion of pixel images and bitmap geometries, see "Bitmap Geometries" beginning on page 5-5.

For more information about the GXNewBitmap function, see its description on
page 5-66. For more information about the GXDrawBitmap function, see its description on page 5-79.

The next section shows you how you can create a bitmap with color information.

Creating Color Bitmaps

All QuickDraw GX bitmaps are actually color bitmaps. A black-and-white bitmap is simply a color bitmap with a color set containing only two colors--black and white.

The sample function in Listing 5-1 on page 5-15 creates a black-and-white bitmap geometry by

The sample function encapsulates the geometry into a bitmap shape with this call to the GXNewBitmap function:

aBitmapShape = GXNewBitmap(&aBitmapGeometry, &initialPosition);
Because the space field of the bitmap geometry specifies the gxNoSpace color space, the GXNewBitmap function chooses a color space for you, based on the pixel size specified in the pixelSize field. QuickDraw GX does not provide any color spaces appropriate for a pixel size of 1, so the GXNewBitmap function creates a grayscale color set with two entries--white and black.

If you specify the gxNoSpace color space with a pixel size of 2, the GXNewBitmap function creates a grayscale color set with four entries--white, light gray, dark gray, and black. After you change the pixel size to 2, you must reflect that change in the pixel image, the bitmap width, and the number of bytes per row.

Typically, if you wanted to make a 1 bit-per-pixel bitmap into a 2 bit-per-pixel bitmap, you would do the following

This method allows you to maintain the size of the bitmap while allowing you to specify more possible values (colors) for each pixel.

However, an easier (if somewhat less useful) way to make a 1 bit-per-pixel bitmap into a 2 bit-per-pixel bitmap is as follows:

Let's see what happens when you apply this simpler method for doubling the pixel size of a bitmap. Doubling the pixel size and halving the bitmap width of the envelope bitmap shown in Figure 5-10 on page 5-20 indicates that QuickDraw GX should interpret every pair of bits in the pixel image as a single pixel. Since each pixel can have one of four possible values (00, 01, 10, 11), the resulting bitmap contains four shades of gray, as shown in Figure 5-11.

Figure 5-11 A bitmap with a grayscale color set (four shades)

You can double the pixel size and halve the bitmap width again, with the following assignments:

aBitmapGeometry.width = 9;
aBitmapGeometry.height = 16;
aBitmapGeometry.rowBytes = 6;
aBitmapGeometry.pixelSize = 4;
QuickDraw GX interprets each set of 4 bits in the pixel image as representing a single pixel of the bitmap, which means each pixel can now be represented by 16 different values (0000, 0001, 0010, and so on). Since QuickDraw GX has no predefined color space that uses a pixel size of 4, it creates for this bitmap a color set with sixteen shades of gray.

Figure 5-12 shows the resulting bitmap.

Figure 5-12 A bitmap with a grayscale color set (sixteen shades)

As the previous examples have shown, setting the space field of a bitmap geometry to the gxNoSpace constant indicates that you want QuickDraw GX to choose a color space for you. In these examples, which had 1, 2, or 4 bits per pixel, QuickDraw GX chose the gxIndexedSpace color space and created a grayscale color set with the appropriate number of color entries.

You are not limited to these grayscale color sets, however. You can create your own color set, by choosing your own set of colors for the color entries. Listing 5-3 shows how to define a simple color set with eight colors--black and white, the three primary RGB colors, and the three secondary RGB colors.

Listing 5-3 Defining a color set

gxColorSet aColorSet;

gxSetColor newColorList[] = {
   {0xFFFF, 0xFFFF, 0xFFFF, 0},     /* white */
   {0xFFFF, 0,      0,      0},     /* red */
   {0,      0xFFFF, 0,      0},     /* green */
   {0,      0,      0xFFFF, 0},     /* blue */
   {0,      0xFFFF, 0xFFFF, 0},     /* cyan */
   {0xFFFF, 0,      0xFFFF, 0},     /* magenta */
   {0xFFFF, 0xFFFF, 0,      0},     /* yellow */
   {0,      0,      0,      0},     /* black */
};
The colors in this color set are specified in the RGB color space, and each color contains four components--the red component, the green component, the blue component, and a fourth component, which QuickDraw GX ignores for the RGB color space. QuickDraw GX allows you to specify colors in other color spaces and with different numbers of components. For complete color-specifying information, see the chapter "Colors and Color-Related Objects" in Inside Macintosh: QuickDraw GX Objects.

Once you've defined the color list for a color set, you create the actual color set object by using the GXNewColorSet function, which requires you to specify the color space in which you've specified the colors, the total number of colors, and the list of colors:

aColorSet = GXNewColorSet(gxRGBSpace, 8, newColorList);
Note
Remember, you are responsible for disposing of QuickDraw GX objects when you no longer need them, so you are responsible for disposing of this new color set.
To use the new color set in your bitmap, you need to set the space and set fields of the bitmap geometry:

aBitmapGeometry.space = gxIndexedSpace;
aBitmapGeometry.set = aColorSet;
Setting the space field to gxIndexedSpace indicates that you are supplying the color set, rather than having QuickDraw GX create one for you.

Figure 5-13 shows the result of applying this new color set to the 4-bits-per-pixel version of the envelope bitmap. Notice that pixel values in the pixel image greater than 7 are out of the range of the color set, so QuickDraw GX maps those pixel values to the color black.

For a color version of Figure 5-13, see Plate 3 at the front of this book.

Figure 5-13 A bitmap with an eight-color color set

Each of the previous examples in this chapter creates a bitmap that uses a color set. QuickDraw GX interprets each pixel of these bitmaps as an index into a set of colors. For example, in the black-and-white bitmap that results from Listing 5-1 on page 5-15, each pixel value (single bit) of the pixel image is an index into a color set with two colors--the index of the color white is 0 and the index of the color black is 1. In the 2 bits-per-pixel example on page 5-6, each pixel value (pair of bits) in the pixel image is an index into a color set with four colors--the index of white is 0 (bits 00), the index of light gray is 1 (bits 01), the index of dark gray is 2 (bits 10), and the index of black is 3 (bits 11).

QuickDraw GX also allows you to create bitmaps that use color spaces other than indexed color spaces (that is, other than color sets). In these bitmaps, each pixel value is an actual color value instead of an index into a list of colors. The chapter "Colors and Color-Related Objects" in Inside Macintosh: QuickDraw GX Objects explains color spaces, color values, color sets, and color indexes.

One example of a bitmap for which you might want to use a color space instead of a color set is a color ramp. A color ramp is a shape that blends from one color into another. Since bitmaps are the only type of QuickDraw GX shape (except picture shapes) that allows multiple colors in one shape, you must implement color ramps as bitmap shapes.

The sample function in Listing 5-4 on page 5-26 shows how to create a simple color ramp. This function declares a bitmap shape reference and a bitmap geometry structure using the declarations

gxShape  aBitmapShape;
gxBitmap aBitmapGeometry;
It then fills in the fields of the bitmap geometry structure. First, it fills in the dimensions:

aBitmapGeometry.width = 1;
aBitmapGeometry.height = 256;
aBitmapGeometry.rowBytes = 1;
aBitmapGeometry.pixelSize = 32;
Notice that the sample function defines the bitmap width to be 1. Later, the sample function uses the GXSetShapeBounds function later to widen the bitmap.

Next, the sample function sets the image field to nil to indicate that QuickDraw GX should allocate memory for the pixel image of the bitmap. The value of the rowBytes field is ignored because QuickDraw GX sets this field when allocating the pixel image.

The sample function then sets the color-related fields of the bitmap geometry structure:

aBitmapGeometry.space = gxRGB32Space;
aBitmapGeometry.set = nil;
aBitmapGeometry.profile = nil;
Notice that the pixel size implied by the color space (which is the gxRGB32Space color space) is the same as the pixel size indicated in the pixelSize field of the bitmap geometry structure (which is 32).

Next, the sample function creates the bitmap shape:

aBitmapShape = GXNewBitmap(&aBitmapGeometry, &initialPosition);
The sample function sets the color values of each pixel in the bitmap shape. To do this, it creates a color structure with the declaration

gxColor current;
Then it fills in the values of the fields of the color structure:

current.space = gxRGBSpace;
current.profile = nil;
current.element.rgb.red = 0xFFFF;  
current.element.rgb.green = 0;       
current.element.rgb.blue = 0;      
current.element.rgb.alpha = 0; 
For a complete discussion of these fields, see the chapter "Colors and Color-Related Objects" in Inside Macintosh: QuickDraw GX Objects.

The sample function then uses the GXSetShapePixel function to set each pixel value in the pixel image of the bitmap shape. Each time the sample function sets the value of a pixel, it changes the color value of the current variable slightly, decreasing the amount of green and increasing the amount of red:

for (count = 0; count < 256; count++) {
   current.element.component[0] -= 0x0101; /* decrease red */
   current.element.component[1] += 0x0101; /* increase green */
      
   GXSetShapePixel(aBitmapShape, 0, count, &current, 0);
}
Finally, the sample function resizes the bitmap, widening it to be a square, and draws the resulting bitmap color ramp. The complete sample function definition is shown in Listing 5-4.

Listing 5-4 Creating a color ramp

void CreateColorRamp(void)
{
   gxShape aBitmapShape;
   gxBitmap aBitmapGeometry;
   const gxPoint initialLocation = {ff(50), ff(50)};
   const gxRectangle theBounds = {ff(50), ff(50), 
                                  ff(150), ff(150)};
   
   gxColor current;
   int count;
   
   /* create a one-pixel-wide bitmap */
   aBitmapGeometry.width = 1;
   aBitmapGeometry.height = 256;
   aBitmapGeometry.rowBytes = 1;
   aBitmapGeometry.pixelSize = 32;
   
   aBitmapGeometry.image = nil;  /* have QuickDraw GX allocate */

   aBitmapGeometry.space = gxRGB32Space;
   aBitmapGeometry.set = nil;
   aBitmapGeometry.profile = nil;

   aBitmapShape = GXNewBitmap(&aBitmapGeometry, &initialLocation);
    
   /* create a red color */
   current.space = gxRGBSpace;
   current.profile = nil;
   current.element.component[0] = 0xFFFF;  /* red */
   current.element.component[1] = 0;       /* green */
   current.element.component[2] = 0;       /* blue */
   current.element.component[3] = 0;       /* alpha */
   
   /* fill in the colors of the bitmap pixel by pixel */
   for (count = 0; count < 256; count++) {
      current.element.rgb.red -= 0x0101;    /* decrease red */
      current.element.rgb.green += 0x0101; /* increase green */
      
      GXSetShapePixel(aBitmapShape, 0, count, &current, 0);
   }

   /* resize the bitmap to give it more width */
   GXSetShapeBounds(aBitmapShape, &theBounds);
   
   GXDrawShape(aBitmapShape);
   GXDisposeShape(aBitmapShape);
}
The resulting color ramp is shown in Figure 5-14. For a color version of this figure, see Plate 4 at the front of this book.

Figure 5-14 A color ramp from red to green

QuickDraw GX provides the ramp library to assist you in creating color ramps. The NewRamp library function requires you to provide a start color, an end color, an integer indicating the number of different colors to calculate in between the start color and end color, and a bounding rectangle for the final color ramp. Listing 5-5 shows how to use this function to create the same color ramp shown in Figure 5-14.

Listing 5-5 Creating a color ramp using the ramp library

void CreateColorRamp(void)
{
   gxShape aBitmapShape;
   gxColor start, end;
   const gxRectangle theBounds = {ff(50), ff(50), 
                                  ff(150), ff(150)};

   start.space = gxRGBSpace;
   start.profile = nil;
   start.element.rgb.red = 0xFFFF;       
   start.element.rgb.green = 0; 
   start.element.rgb.blue = 0;       
   start.element.rgb.alpha = 0;     

   end.space = gxRGBSpace;
   end.profile = nil;
   end.element.rgb.red = 0;       
   end.element.rgb.green = 0xFFFF;  
   end.element.rgb.blue = 0;     
   end.element.rgb.alpha = 0;     

   aBitmapShape = NewRamp(&start, &end, 256, &theBounds);
   GXDrawShape(aBitmapShape);
   GXDisposeShape(aBitmapShape);
}
As a further convenience, QuickDraw GX provides the color library, which allows you to use predefined constants to specify frequently used colors. You provide the SetCommonColor library function with a pointer to a color structure, and a predefined constant specifying the color you want.

This function then initializes the color structure with the appropriate values to represent the color you specify.

For example, the following call sets the fields of the start color structure with the values that represent the color red:

SetCommonColor(&start, red);
Listing 5-6 shows you how to create the color ramp in Figure 5-14 by using functions from both the ramp and color libraries.

Listing 5-6 Creating a color ramp using both the ramp and color libraries

void CreateColorRamp(void)
{
   gxShape  aBitmapShape;
   gxColor start, end;
   const gxRectangle theBounds = {ff(50), ff(50), 
                                  ff(150), ff(150)};

   SetCommonColor(&start, red);
   SetCommonColor(&end, green);

   aBitmapShape = NewRamp(&start, &end, 0, &theBounds);
   GXDrawShape(aBitmapShape);
   GXDisposeShape(aBitmapShape);
}
For a discussion of pixel images and bitmap geometries, see "Bitmap Geometries" beginning on page 5-5.

You can find more information about colors, color structures, color values, color sets, and color spaces in the chapter "Colors and Color-Related Objects" in Inside Macintosh: QuickDraw GX Objects.


Previous Book Contents Book Index Next

© Apple Computer, Inc.
7 JUL 1996




Navigation graphic, see text links

Main | Page One | What's New | Apple Computer, Inc. | Find It | Contact Us | Help